﻿using System.Diagnostics.CodeAnalysis;
using DimensionsHelper.LanguageService.Protocol;

namespace DimensionsHelper.LanguageService.Syntax;

public readonly struct SyntaxLocation : IEquatable<SyntaxLocation>, IComparable<SyntaxLocation>
{
    [Flags]
    private enum LocationFlags
    {
        None = 0,
        IsComment = 1 << 0,
        IsActive = 1 << 1,
        IsUnreachable = 1 << 2,
        IsUnused = 1 << 3
    }


    private SyntaxLocation(DocumentUri uri, int start, int width, LocationFlags flags)
    {
        Uri = uri;
        Start = start;
        Width = width;
        _flags = flags;
    }


    private readonly LocationFlags _flags;

    public DocumentUri Uri { get; }

    public TextSpan Span => TextSpan.Create(Uri, Start, End);

    public int Start { get; }

    public int End => Start + Width;

    public int Width { get; }

    public bool IsEmpty => Width == 0;

    public bool IsComment => _flags.HasFlag(LocationFlags.IsComment);

    public bool IsUnreachable => _flags.HasFlag(LocationFlags.IsUnreachable);

    public bool IsActive => _flags.HasFlag(LocationFlags.IsActive);

    public bool IsUnused => _flags.HasFlag(LocationFlags.IsUnused);


    public void Offset(int offset)
    {
        throw new NotSupportedException("SyntaxLocation is read-only.");
    }


    public static SyntaxLocation Create(DocumentUri uri, int start, int length,
        bool isComment = false, bool isActive = true, bool isUnused = false, bool isUnreachable = false)
    {
        LocationFlags flags = LocationFlags.None;

        if (isComment)
        {
            flags |= LocationFlags.IsComment;
        }

        if (isActive)
        {
            flags |= LocationFlags.IsActive;
        }

        if (isUnused)
        {
            flags |= LocationFlags.IsUnused;
        }

        if (isUnreachable)
        {
            flags |= LocationFlags.IsUnreachable;
        }

        return new SyntaxLocation(uri, start, length, flags);
    }


    public static SyntaxLocation CreateUnreachable(DocumentUri uri, int start, int length)
    {
        return new SyntaxLocation(uri, start, length,
            LocationFlags.IsUnreachable | LocationFlags.IsActive);
    }


    public static SyntaxLocation CreateUnused(DocumentUri uri, int start, int length)
    {
        return new SyntaxLocation(uri, start, length,
            LocationFlags.IsUnused | LocationFlags.IsActive);
    }


    public static SyntaxLocation CreateInActive(DocumentUri uri, int start, int length)
    {
        return new SyntaxLocation(uri, start, length, LocationFlags.None);
    }


    public int CompareTo(SyntaxLocation other)
    {
        int res = Start - other.Start;

        if (res == 0)
        {
            return End - other.End;
        }

        return res;
    }

    public bool Equals(SyntaxLocation location)
    {
        return Uri.Equals(location.Uri) &&
               Start == location.Start &&
               Width == location.Width &&
               _flags == location._flags;
    }

    public override bool Equals([NotNullWhen(true)] object? obj)
    {
        return obj is SyntaxLocation other && Equals(other);
    }

    public override int GetHashCode()
    {
        return HashCode.Combine(Uri, Start, Width, _flags);
    }

    public static bool operator ==(SyntaxLocation left, SyntaxLocation right)
    {
        return left.Equals(right);
    }

    public static bool operator !=(SyntaxLocation left, SyntaxLocation right)
    {
        return !(left == right);
    }


    public override string ToString()
    {
        string fileName = DocumentUri.UriToFsPath(Uri, true);
        return $"[{Start}..{End}) - {fileName}";
    }
}